/*
 * FeedRepresentation.java
 *
 * Created on March 27, 2007, 11:55 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.app;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.sql.SQLException;
import java.util.Date;
import java.util.Iterator;
import java.util.UUID;
import java.util.logging.Level;
import org.atomojo.app.auth.User;
import org.atomojo.app.db.DB.MediaEntryListener;
import org.atomojo.app.db.Entry;
import org.atomojo.app.db.Feed;
import org.atomojo.app.db.EntryMedia;
import org.atomojo.app.util.Copy;
import org.atomojo.app.util.Move;
import org.infoset.xml.Document;
import org.infoset.xml.DocumentLoader;
import org.infoset.xml.Element;
import org.infoset.xml.sax.SAXDocumentLoader;
import org.restlet.Application;
import org.restlet.data.CharacterSet;
import org.restlet.data.Form;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.data.Tag;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;

/**
 *
 * @author alex
 */
public class MediaResource extends AtomResource {

   EntryMedia media;
   App app;

   /** Creates a new instance of FeedRepresentation */
   public MediaResource(Application app,Feed feed,EntryMedia media,Storage storage) {
      super(app,feed,storage);
      this.media = media;
      this.app = new App(getContext().getLogger(),feed.getDB(),storage,theApp.getMetadataService());
   }
   
   public EntryMedia getMedia() {
      return media;
   }
   
   
   public Representation get() {
      try {
         Date modifiedSince = getRequest().getConditions().getUnmodifiedSince();
         if (modifiedSince!=null) {
            getLogger().info("Modified since "+modifiedSince+", edited="+media.getEdited());
         }
         if (modifiedSince==null || modifiedSince.getTime()<media.getEdited().getTime()) {
            Representation rep = storage.getMedia(feed.getPath(),feed.getUUID(),media.getName());
            rep.setMediaType(media.getMediaType());
            getResponse().setStatus(Status.SUCCESS_OK);
            return rep;
         } else {
            getResponse().setStatus(Status.REDIRECTION_NOT_MODIFIED);
            return null;
         }
      } catch (IOException ex) {
         getContext().getLogger().log(Level.SEVERE,"Cannot get media: "+ex.getMessage(),ex);
         getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
         return new StringRepresentation("Cannot retrieve media.");
      } catch (SQLException ex) {
         getContext().getLogger().log(Level.SEVERE,"Cannot get media: "+ex.getMessage(),ex);
         getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
         return new StringRepresentation("Cannot retrieve media.");
      }
   }
   
   public Representation put(Representation entity) {
      try {
         media.setMediaType(entity.getMediaType());
         Status status = storage.storeMedia(feed.getPath(),feed.getUUID(),media.getName(),entity.getMediaType(),entity.getStream());
         if (!status.isSuccess()) {
            getResponse().setStatus(status);
            return new StringRepresentation("Cannot store media.");
         }
         media.touch();
         
         // get the media resoruce's entry
         Entry entry = media.getEntry();
         
         // Get the entry's representation
         String feedBaseURI = getRequest().getResourceRef().getParentRef().getPath();
         Representation entryRep = app.getEntryRepresentation(feedBaseURI,feed,entry.getUUID());
         // avoid a thread with the entry's output representation
         StringWriter sw = new StringWriter();
         entryRep.write(sw);
         entryRep.release();
         DocumentLoader loader = new SAXDocumentLoader();
         Document doc = loader.load(new StringReader(sw.toString()));
         
         // mark the entry as edited
         Date date = media.getEntry().edited();
         
         // change the entry's edited element
         Element edited = doc.getDocumentElement().getFirstElementNamed(AtomResource.EDITED_NAME);
         if (edited!=null) {
            edited.clear();
            edited.addCharacters(toXSDDate(date));
         }
         
         // store the change.
         status = storage.storeEntry(feed.getPath(),feed.getUUID(),entry.getUUID(),doc);
         if (status.isSuccess()) {
            getResponse().setStatus(Status.SUCCESS_NO_CONTENT);
            return null;
         } else {
            getResponse().setStatus(status);
            return new StringRepresentation("Cannot update entry for media.");
         }
         
      } catch (Exception ex) {
         getContext().getLogger().log(Level.SEVERE,"Cannot update media type: "+ex.getMessage(),ex);
         getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
         return new StringRepresentation("Cannot update media.");
      }
   }
   
   public Representation delete()
   {
      try {
         final String fpath = feed.getPath();
         Entry entry = media.getEntry();
         entry.delete(new MediaEntryListener() {
            public void onDelete(EntryMedia resource) {
               try {
                  storage.deleteMedia(fpath,feed.getUUID(),resource.getName());
               } catch (IOException ex) {
                  getContext().getLogger().log(Level.SEVERE,"Cannot delete media: "+ex.getMessage(),ex);
               }
            }
         });
         if (storage.deleteEntry(fpath,feed.getUUID(),entry.getUUID())) {
            getResponse().setStatus(Status.SUCCESS_NO_CONTENT);
            return null;
         } else {
            getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
            return new StringRepresentation("Cannot delete entry document.");
         }
      } catch (Exception ex) {
         getContext().getLogger().log(Level.SEVERE,"Exception while deleting entry: "+ex.getMessage(),ex);
         getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
         return new StringRepresentation("Exception while deleting entry.");
      }
   }
   
   public Representation head() {
      try {
         Representation dbRep = storage.getMediaHead(feed.getPath(),feed.getUUID(),media.getName());
         dbRep.setMediaType(media.getMediaType());
         String charset = dbRep.getMediaType().getParameters().getFirstValue("charset");
         if (charset!=null) {
            dbRep.setCharacterSet(CharacterSet.valueOf(charset));
         }
         getResponse().setStatus(Status.SUCCESS_NO_CONTENT);
         return dbRep;
      } catch (IOException ex) {
         getContext().getLogger().log(Level.SEVERE,"Exception while getting media: "+ex.getMessage(),ex);
         getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
         return new StringRepresentation("Exception while getting entry.");
      } catch (SQLException ex) {
         getContext().getLogger().log(Level.SEVERE,"Exception while getting media: "+ex.getMessage(),ex);
         getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
         return new StringRepresentation("Exception while getting entry.");
      }
   }
   /*
   public void handleOptions() {
      super.handleOptions();
      Form headers = (Form)getResponse().getAttributes().get("org.restlet.http.headers");
      if (headers==null) {
         headers = new Form();
         getResponse().getAttributes().put("org.restlet.http.headers",headers);
      }
      headers.add("DAV", "1");
   }
   public void handlePropfind() {
      Form headers = (Form)getResponse().getAttributes().get("org.restlet.http.headers");
      if (headers==null) {
         headers = new Form();
         getResponse().getAttributes().put("org.restlet.http.headers",headers);
      }
      headers.add("DAV", "1");
      if (getRequest().isEntityAvailable()) {
         Form requestHeaders = (Form)getRequest().getAttributes().get("org.restlet.http.headers");
         String value = requestHeaders==null ? null : requestHeaders.getFirstValue("Depth");
         final PropfindHandler propfind = new PropfindHandler(getLogger());
         propfind.setRequestDepth(0);
         try {
            String xml = getRequest().getEntity().getText();
            getLogger().info(xml);
            propfind.loadRequest(new StringReader(xml));
         } catch (Exception ex) {
            getLogger().log(Level.SEVERE,"Cannot parse entity text.",ex);
         }
         getResponse().setStatus(Status.SUCCESS_MULTI_STATUS);
         getResponse().setEntity(new OutputRepresentation(MediaType.APPLICATION_XML) {
            public void write(OutputStream os) 
               throws IOException
            {
               Writer w = new OutputStreamWriter(os,"UTF-8");
               propfind.handle(w,new FeedResource.MediaDAVResource(getRequest().getResourceRef().toString(), media));
               w.flush();
            }
         });
         getResponse().getEntity().setCharacterSet(CharacterSet.UTF_8);
      }
   }
    */

   @Move
   public Representation move() {
      Form headers = (Form)getRequest().getAttributes().get("org.restlet.http.headers");
      String slug = headers.getValues("slug");
      if (slug==null) {
         slug = headers.getValues("Slug");
      }
      if (slug==null) {
         getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
         return new StringRepresentation("Missing the 'Slug' header for the move target");
      }
      try {
         // Decode the precent encoding of the UTF-8 values
         slug = URLDecoder.decode(slug,"UTF-8");
         // Encode the slug as a URL encoding
         slug = URLEncoder.encode(slug,"UTF-8");
      } catch (UnsupportedEncodingException ex) {
         getLogger().log(Level.SEVERE,"Cannot decode slug value: "+slug,ex);
         getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
         return new StringRepresentation("Bad 'Slug' header value: "+slug);
      }
      try {
         Representation entity = storage.getMedia(feed.getPath(),feed.getUUID(),media.getName());
         entity.setMediaType(media.getMediaType());
         
         if (feed.findEntryResource(slug)!=null) {
            getResponse().setStatus(Status.CLIENT_ERROR_CONFLICT);
            return new StringRepresentation("Resource already exists with name "+slug);
         }
         
         // TODO: move should use rename
         Status status = storage.storeMedia(feed.getPath(),feed.getUUID(),slug,entity.getMediaType(),entity.getStream());
         if (!status.isSuccess()) {
            getResponse().setStatus(status);
            return new StringRepresentation("Cannot store media to "+slug);
         }
         if (!storage.deleteMedia(feed.getPath(), feed.getUUID(), media.getName())) {
            getLogger().severe("Cannot delete previous entry storage "+feed.getPath()+","+feed.getUUID()+","+feed.getName());
         }
         media.setName(slug);
         media.touch();
         
         
         // get the media resoruce's entry
         Entry entry = media.getEntry();
         
         // Get the entry's representation
         String feedBaseURI = getRequest().getResourceRef().getParentRef().getPath();
         Representation entryRep = app.getEntryRepresentation(feedBaseURI,feed,entry.getUUID());
         // avoid a thread with the entry's output representation
         StringWriter sw = new StringWriter();
         entryRep.write(sw);
         entryRep.release();
         DocumentLoader loader = new SAXDocumentLoader();
         Document doc = loader.load(new StringReader(sw.toString()));
         
         // mark the entry as edited
         Date date = media.getEntry().edited();
         
         // change the entry's edited element
         Element edited = doc.getDocumentElement().getFirstElementNamed(AtomResource.EDITED_NAME);
         if (edited!=null) {
            edited.clear();
            edited.addCharacters(toXSDDate(date));
         }
         Element content = doc.getDocumentElement().getFirstElementNamed(AtomResource.CONTENT_NAME);
         content.setAttributeValue("src",slug);
         
         Iterator<Element> links = doc.getDocumentElement().getElementsByName(AtomResource.LINK_NAME);
         while (links.hasNext()) {
            Element link = links.next();
            if ("edit-media".equals(link.getAttributeValue("rel"))) {
               link.setAttributeValue("href", slug);
            }
         }
         
         // store the change.
         status = storage.storeEntry(feed.getPath(),feed.getUUID(),entry.getUUID(),doc);
         if (status.isSuccess()) {
            getResponse().setStatus(Status.SUCCESS_OK);
            Form returnHeaders = new Form();
            getResponse().getAttributes().put("org.restlet.http.headers",returnHeaders);
            Reference ref = new Reference(getRequest().getResourceRef().getParentRef()+"_/"+entry.getUUID().toString());
            getResponse().setLocationRef(ref);
            Representation rep = app.getEntryRepresentation(getRequest().getResourceRef().getParentRef().toString(),entry);
            rep.setCharacterSet(CharacterSet.UTF_8);
            rep.setTag(new Tag(Long.toString(entry.getEdited().getTime()),false));
            return rep;
         } else {
            getResponse().setStatus(status);
            return new StringRepresentation("Cannot update entry for media.");
         }
         
      } catch (Exception ex) {
         getContext().getLogger().log(Level.SEVERE,"Cannot move media.",ex);
         getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
         return new StringRepresentation("Cannot move media.");
      }
   }

   @Copy
   public Representation copy() {
      Form headers = (Form)getRequest().getAttributes().get("org.restlet.http.headers");
      String slug = headers.getValues("slug");
      if (slug==null) {
         slug = headers.getValues("Slug");
      }
      if (slug==null) {
         getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
         return new StringRepresentation("Missing the 'Slug' header for the move target");
      }
      try {
         // Decode the precent encoding of the UTF-8 values
         slug = URLDecoder.decode(slug,"UTF-8");
         // Encode the slug as a URL encoding
         slug = URLEncoder.encode(slug,"UTF-8");
      } catch (UnsupportedEncodingException ex) {
         getLogger().log(Level.SEVERE,"Cannot decode slug value: "+slug,ex);
         getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
         return new StringRepresentation("Bad 'Slug' header value: "+slug);
      }
      try {

         // Get author name for identity
         User user = (User)getRequest().getAttributes().get(App.USER_ATTR);
         
         Representation entity = storage.getMedia(feed.getPath(),feed.getUUID(),media.getName());
         entity.setMediaType(media.getMediaType());
         
         UUID id = UUID.randomUUID();
         
         Entry entry = app.createMediaEntry(user,feed,entity,slug,id);

         getResponse().setStatus(Status.SUCCESS_CREATED);
         headers = new Form();
         getResponse().getAttributes().put("org.restlet.http.headers",headers);
         Reference ref = new Reference(getRequest().getResourceRef().getParentRef()+"_/"+entry.getUUID().toString());
         getResponse().setLocationRef(ref);
         Representation rep = app.getEntryRepresentation(getRequest().getResourceRef().getParentRef().toString(),entry);
         rep.setCharacterSet(CharacterSet.UTF_8);
         rep.setTag(new Tag(Long.toString(entry.getEdited().getTime()),false));
         return rep;
      } catch (AppException ex) {
         getResponse().setStatus(ex.getStatus());
         if (ex.getStatus().getCode()==Status.SERVER_ERROR_INTERNAL.getCode()) {
            getContext().getLogger().log(Level.SEVERE,ex.getMessage(),ex.getCause());
            return new StringRepresentation("Internal error, see logs.");
         } else {
            return new StringRepresentation(ex.getMessage());
         }
      } catch (Exception ex) {
         getContext().getLogger().log(Level.SEVERE,"Cannot copy media.",ex);
         getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
         return new StringRepresentation("Internal error, see logs.");
      }
   }
}
